package work.month06.day28;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;

/**
 * @date 2020/06/28
 * @author 王光浩
 * @introduction 实现文件编码的转换
 */
public class FileEncodingConvertor {

	private final static Charset UFT_8 = Charset.forName("utf-8");
	private final static Charset GBK = Charset.forName("gbk");

	/**
	 * 私有化构造器
	 */
	private FileEncodingConvertor() {

	}

	/**
	 * 通过静态方法获取 FileEncodingConvertor 对象
	 * @return
	 */
	public static FileEncodingConvertor getInstance() {
		return new FileEncodingConvertor();
	}

	/**
	 * 将 originCharset 编码的  originFile 文件转化为 destCharset 编码格式的 的文件，然后写入到 destFile 中
	 * 需要注意：不支持视频、音频、图片等方式的转化
	 * @param originFile 源文件
	 * @param originCharset 源文件编码方式
	 * @param destFile 目标文件
	 * @param destCharset 目标文件编码方式
	 */
	public void convert(File originFile, Charset originCharset, File destFile, Charset destCharset) {
		// 1. 参数检查
		if (originFile == null || destFile == null || !originFile.isFile() || !destFile.isFile())
			throw new IllegalArgumentException("参数异常");

		// 2. 相关变量的声明与初始化
		InputStream in = null;
		InputStreamReader reader = null;
		OutputStream out = null;
		OutputStreamWriter writer = null;
		try {
			in = new FileInputStream(originFile);
			out = new FileOutputStream(destFile);
			reader = new InputStreamReader(in, originCharset);
			writer = new OutputStreamWriter(out, destCharset);
		} catch (FileNotFoundException e) {
			try {
				this.close(reader, writer, in, out);
			} catch (Exception e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		} 
		// 3. 文件内容的读取与写入
		char[] buffer = new char[1024]; // 一次读取 1024 个字符
		int len = 0;
		try {
			// 使用 while 循环进行读和写
			while ((len = reader.read(buffer)) != -1) {
				writer.write(buffer, 0, len);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 4. 资源的关闭
			try {
				this.close(reader, writer, in, out);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	/**
	 * 完成某个目录下的所有文件的编码转换操作
	 * @param originDirectory 表示需要批量转换编码的文件所在的目录
	 * @param originCharset 表示原始文件编码
	 * @param destDirectory 表示批量转换编码后的文件存储目录
	 * @param destCharset 表示目标文件编码
	 */
	public void batch(File originDirectory, Charset originCharset, File destDirectory, Charset destCharset) {
		// 1. 条件测试
		if (originDirectory == null || destDirectory == null || !originDirectory.isDirectory()
				|| !destDirectory.isDirectory())
			throw new IllegalArgumentException("非法参数");
		// 2. 相关变量的声明与初始化
		InputStream in = null;
		InputStreamReader reader = null;
		OutputStream out = null;
		OutputStream writer = null;
		File temp = null;
		String destDic = null; // 用于存在需要存储的路径的前缀部分
		File[] inputFiles = originDirectory.listFiles(); // 获取源目录下所有的文件对象
		

		// 3. 将文件进行转码
		try {
			destDic = destDirectory.getCanonicalPath() + "/";
			
			for (File f : inputFiles) {
				// 3.1 如果 f 不是文件则跳过
				if (!f.isFile()) {
					continue;
				}
				// 3.2 在指定目录下创建一个新的文件，如果该文件已经存在则覆盖
				temp = new File(destDic + f.getName());
				if ( !temp.exists() )
					temp.createNewFile();
				// 3.3 调用上转化单个文件编码的方法
				this.convert(f, originCharset, temp, destCharset);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
		// 4. 当出现异常时资源的关闭
			try {
				this.close(reader, writer, in, out);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	/**
	 * 单纯实现将 GBK 编码的文件转换为 UTF-8 编码
	 * @param originFile 使用 GBK 编码的源文件
	 * @param destFile 目标文件的存放处（使用 UTF-8 ）
	 */
	public void toUTF8( File originFile , File destFile ) {
		this.convert(originFile, GBK, destFile, UFT_8);
	}
	
	/**
	 * 单纯实现将 UTF-8 编码的文件转换为 GBK 编码
	 * @param originFile 使用 UTF-8 编码的源文件
	 * @param destFile  使用 GBK 编码的目标文件
	 */
	public void toGBK( File originFile , File destFile ) {
		this.convert(originFile, UFT_8, destFile, GBK);
	}
	
	/**
	 * 用于关闭资源
	 * @param source 需要关闭的资源（要求实现 AutoCloseable 接口）
	 * @throws Exception
	 */
	private void close(AutoCloseable... sources) throws Exception {
		for (AutoCloseable source : sources) {
			if (source != null)
				source.close();
		}
	}
}
